Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config: Struct opaque filter proto config support, LDS/RDS integratio… #1495

Merged
merged 10 commits into from
Aug 22, 2017

Conversation

htuch
Copy link
Member

@htuch htuch commented Aug 18, 2017

…n test.

This PR provides an integration test demoing LDS/RDS/CDS/EDS. Along the way, needed to solve the
problem of plumbing in the new HTTP connection manager filter proto config, rather than the legacy
v1 JSON. The solution adopted in this PR is to use a "deprecatedV1" field in the opaque config to
determine if we're doing legacy config or v2 proto.

…n test.

This PR provides an integration test demoing LDS/RDS/CDS/EDS. Along the way, needed to solve the
problem of plumbing in the new HTTP connection manager filter proto config, rather than the legacy
v1 JSON. The solution adopted in this PR is to use a "deprecatedV1" field in the opaque config to
determine if we're doing legacy config or v2 proto.
@htuch
Copy link
Member Author

htuch commented Aug 18, 2017

@mattklein123 I think this is actually cleaner, if you look at the example configs. There's no need for the @type foo and I think we don't want to make a choice based on ASCII PB being a thing going forward based on what @louiscryan says.

Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah configs look very readable to me. +1. Will do full review when passing tests.

@@ -207,6 +207,13 @@ class IntegrationTestServer : Logger::Loggable<Logger::Id::testing>,
}
void start(const Network::Address::IpVersion version);
void start();

void waitForCounterGe(const std::string& name, uint64_t value) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know there are other cases where we are doing this (In fact IIRC you implemented one at some point, and I think there may be others)? I might be making this up. If there are can we replace with this?

@@ -48,10 +49,26 @@ class RepeatedPtrUtil {
class MessageUtil {
public:
static std::size_t hash(const Protobuf::Message& message) {
return std::hash<std::string>{}(message.SerializeAsString());
std::string text;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity what was wrong with the old version? Maybe a small comment on what all of this is for?

Copy link
Member Author

@htuch htuch Aug 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but it was causing test flakes, since the non-deterministic proto serialization led to distinct hashes for the same proto.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK makes sense. Maybe just a small comment?

}

static void loadFromJson(const std::string json, Protobuf::Message& message);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the reason we are passing std::string here vs reference string issues again? If so can we put a TODO somewhere?

@@ -58,6 +58,7 @@ typedef std::unique_ptr<ListenerImpl> ListenerImplPtr;
COUNTER(listener_added) \
COUNTER(listener_modified) \
COUNTER(listener_removed) \
COUNTER(listener_create_success) \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to get added to docs.

Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more random comments and some questions. I'm a little confused.

}

/**
* @return std::unique_ptr<Protobuf::Message> create empty config proto message for v2. The filter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: worth defining Protobuf::MessagePtr? I think we will have a lot of this.

@@ -4,8 +4,10 @@
#include <string>
#include <vector>

#include "google/protobuf/any.pb.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still using any?

NetworkFilterFactoryCb HttpConnectionManagerFilterConfigFactory::createFilterFactoryFromProto(
const Protobuf::Message& config, FactoryContext& context) {
return createHttpConnectionManagerFilterFactory(
dynamic_cast<const envoy::api::v2::filter::HttpConnectionManager&>(config), context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to check my understanding here: For a struct, whether JSON or proto, the type if embedded, so when it is actually parsed, it becomes the real object? Is that right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type doesn't appear in the config at all, all we have is a google.protobuf.Struct knowledge when we encounter an opaque config. We then convert this Struct to JSON and then load it back into a Protobuf::Message (superclass of all concrete proto types), by invoking Protobuf::util::JsonStringToMessage(json, &message). This function makes use of the type information in the supplied message to figure out how to do the parsing of the JSON.

This is why we need createConfigProto(), it's essentially providing a JSON->message conversion factory via the proto message it returns. If you can think of a cleaner way to do this, I'm open to that as well (we could literally renamed to createConfigProtoFactory and give it some more abstract interface if you prefer).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that HttpConnectionManager message actually never appears on the wire, but is used internally by Envoy only? I.e., to pass in http connection manager config via a protobuf I have to map from the HttpConnectionManager message definition to a protobuf Struct, field by field. If so, what is the point of specifying the HttpConnectionMananger message in the envoy-api?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could not figure it out, managed to get it to work by encoding to Struct, seems to work but looks quite inefficient coding-wise:

    filter_chains {
      filters {
        name: "http_connection_manager"
        config {
          fields {
            key: "rds"
            value {
              struct_value {
                fields {
                  key: "config_source"
                  value {
                    struct_value {
                      fields {
                        key: "api_config_source"
                        value {
                          struct_value {
                            fields {
                              key: "api_type"
                              value {
                                number_value: 2
                              }
                            }
                            fields {
                              key: "cluster_name"
                              value {
                                string_value: "rdsCluster"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
                fields {
                  key: "route_config_name"
                  value {
                    string_value: "router1"
                  }
                }
              }
            }
          }
          fields {
            key: "stat_prefix"
            value {
              string_value: "proxy"
            }
          }
        }
        deprecated_v1 {
          type: "read"
        }
      }
    }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it only appears as a Struct. What you have is correct. The goal here is not efficiency, but being extensible, we don't want to predefine all filter config protos upfront, we want to support user extension. The alternative google.protobuf.Any type was not attractive as it is hard to do anything with the encoded proto without the original schema, e.g. dumping debug, assembling in config generation tools.

This is why we use JSON/YAML for textual representation, the ugliness of Struct is hidden.

Copy link
Member Author

@htuch htuch Sep 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm interested in understanding the efficiency requirements here. Two questions:

  1. Given this is control plane, I wouldn't normally be that concerned about slight encoding inefficiency. Do you have some very significant scaling requirements in terms of the size/frequency of config update?
  2. I haven't looked at the wire format for Struct, but I assume the main objection is the fact that the key string is included, which is not needed if you know the proto upfront.

TBH, this is part of the API which we've tentatively frozen, so it would take a compelling reason to make this change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any numbers now, but you are likely right in that performance difference does not matter here. Even so it seems to me that there is much bigger difference in encoding/decoding CPU/memory use than in the wire format. Given that one of the main rationales of protobufs vs. XML (for example) is >10x encoding/decoding performance difference this seems like going backwards.

On the second point the change could be made backwards compatible by keeping the code point 2 for Struct config, and adding new code point(s) for Any or Oneof Message configs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best thing to do at this point is open an issue and let's see what others in the community think.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #1680.

Stats::CounterSharedPtr counter(const std::string& name) {
// When using the thread local store, only counters() is thread safe. This also allows us
// to test if a counter exists at all versus just defaulting to zero.
return TestUtility::findCounter(*stat_store_, name);
}

Stats::GaugeSharedPtr gauge(const std::string& name) {
return TestUtility::findGauge(*stat_store_, name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we add variant of comment in counter() to here about thread safety.

* config will be parsed into the result of this method. Optional today, will be
* compulsory when v1 is deprecated.
*/
virtual std::unique_ptr<Protobuf::Message> createConfigProto() { return nullptr; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I'm a little confused. I'm trying to understand why we need a createConfigProto() function in addition to the new style factory function. (I think I'm getting mixed up with how we are going from proto to JSON and then back to proto, etc.). I assume this is because we need a well typed structure to jam JSON into, but isn't that somehow known from the type inside it? Is there any way to make this more clear?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you say above is correct, except for the bit about the "type inside". There's no implied type in a google.protobuf.Struct. If we had gone down the route of google.protobuf.Any, there would be a type URL present in the JSON that we could use to perform this conversion without the filter having to supply this factory (see the other comment on this topic for more discussion of JSON->message).

rds:
routeConfigName: route_config_0
configSource: { path: {{ rds_json_path }} }
httpFilters: [{ name: router, config: { deprecatedV1: true }}]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from json to yaml? and user configs have to mention these weird strings
"@type": type.googleapis.com/envoy.api.v2.Listener ? :(

Copy link
Member Author

@htuch htuch Aug 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what the "from json to yaml" is referring to, can you clarify?

On the @type annotations, these are a product of the generic handling of subscription types and ADS multiplexing support. I don't expect we'll see this stuff in hand-written configs in v2, instead, we will allow the bootstrap to specify both listeners and clusters, like v1, so this should only show up in test files like the above, or where folks want filesystem based dynamic config. I agree it's a bit ugly, but hand-written configs are really only supported in a best effort manner, one of the v2 API principles is that the API is primarily for machines.

* config will be parsed into the result of this method. Optional today, will be
* compulsory when v1 is deprecated.
*/
virtual ProtobufTypes::MessagePtr createConfigProto() { return nullptr; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per offline convo I think we decided to switch to createEmptyConfigProto() and have a slightly more robust comment about struct -> JSON -> message. Otherwise change LGTM.

@htuch htuch merged commit 4e63a02 into envoyproxy:master Aug 22, 2017
@htuch htuch deleted the lds-struct branch August 22, 2017 16:14
rshriram added a commit to rshriram/envoy that referenced this pull request Oct 30, 2018
Signed-off-by: Shriram Rajagopalan <[email protected]>
jpsim pushed a commit that referenced this pull request Nov 28, 2022
Description:
- Connection classes will open minimum of 2 under most circumstances to a given endpoint and distribute requests between them (previously, only 1).
- Fixes platform-bridged filters crash when resumed asynchronously after stream termination.
- Adds Pulse support for stats tags.
- Enables configuration of stream idle timeout.
- Disables route timeout by default.
- Introduces a Python interface compatible with the popular Requests library.
- Adds experimental QUIC integration test.
- Adds pure JVM support.

Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: JP Simard <[email protected]>
jpsim pushed a commit that referenced this pull request Nov 29, 2022
Description:
- Connection classes will open minimum of 2 under most circumstances to a given endpoint and distribute requests between them (previously, only 1).
- Fixes platform-bridged filters crash when resumed asynchronously after stream termination.
- Adds Pulse support for stats tags.
- Enables configuration of stream idle timeout.
- Disables route timeout by default.
- Introduces a Python interface compatible with the popular Requests library.
- Adds experimental QUIC integration test.
- Adds pure JVM support.

Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: JP Simard <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants